#include "StdAfx.h"

#include "utils/Errors.h"
#include "utils/Files.h"
#include "utils/SafeCast.h"

#include "TagSourceDirectory.h"
#include "TagStorage.h"
#include "TagPreferences.h"


#pragma warning(push)
#pragma warning(disable : 4355) // 'this' : used in base member initializer list
TagSourceDirectory::TagSourceDirectory(TagStorage* storage, const char* path)
: TagSource(storage)
, directoryWatch_(new DirectoryWatch(this))
, directory_(this)
, changed_(false)
{
	path_ = path;
	tagFilePath_ = path;
	tagFilePath_ += Files::PATH_SEPARATOR;
	tagFilePath_ += "tags";

	directory_.attach(path, true);

	//VERIFY(directoryWatch_->install(path, true));
}
#pragma warning(pop)

TagSourceDirectory::~TagSourceDirectory()
{
	directoryWatch_->uninstall();
	delete directoryWatch_;
}

void TagSourceDirectory::rescan()
{
}

TagNode* TagSourceDirectory::findNodeByPath(const char* path, bool filePath)
{
	size_t len = strlen(path);
	const char* end = path + len;
	const char* p = path;
	const char* lastSep = path;
	
	TagNode* currentNode = &directory_;
	while(true){
		while(p != end && *p != Files::PATH_SEPARATOR[0])
			++p;
		if(filePath && p == end)
			return currentNode;
		else if(TagNode* node = currentNode->findByName(std::string(lastSep, p).c_str())){
			currentNode = node;
			if(p == end)
				return currentNode;
			else{
				++p;
				lastSep = p;
			}
		}
		else
			return 0;
	}
}

void TagSourceDirectory::onFileAdded(DirectoryWatch* watch, const char* path)
{
	if(TagNodeDirectory* dir = safe_cast<TagNodeDirectory*>(findNodeByPath(path)))
		if(dir->onFileAdded(Files::extractFileName(path)))
			changed_ = true;

}

void TagSourceDirectory::onFileRemoved(DirectoryWatch* watch, const char* path)
{
	if(TagNodeDirectory* dir = safe_cast<TagNodeDirectory*>(findNodeByPath(path)))
		if(dir->onFileRemoved(Files::extractFileName(path)))
			changed_ = true;
}


void TagSourceDirectory::onFileModified(DirectoryWatch* watch, const char* path)
{
	if(TagNodeDirectory* dir = safe_cast<TagNodeDirectory*>(findNodeByPath(path)))
		if(dir->onFileModified(Files::extractFileName(path)))
			changed_ = true;
}

void TagSourceDirectory::onFileRenamed(DirectoryWatch* watch, const char* oldName, const char* newName)
{
	if(TagNodeDirectory* dir = safe_cast<TagNodeDirectory*>(findNodeByPath(oldName)))
		if(dir->onFileRenamed(Files::extractFileName(oldName), Files::extractFileName(newName)))
			changed_ = true;
}

bool TagSourceDirectory::poll()
{
	changed_ = false;
	directory_.poll();
	return changed_;
}

// ---------------------------------------------------------------------------

TagNodeDirectory::TagNodeDirectory(TagNodeDirectory* parent)
: TagNode(parent->storage())
, DirectoryWatch(parent->source())
, source_(parent->source())
{

}

TagNodeDirectory::TagNodeDirectory(TagSourceDirectory* source)
: TagNode(source->storage())
, DirectoryWatch(source)
, source_(source)
{

}

bool TagNodeDirectory::poll()
{
	return DirectoryWatch::poll();
}


bool TagNodeDirectory::attach(const char* path, bool root)
{
	if(!Files::isDirectory(path))
		return false;
	if(root){
		if(!install(path, true))
			return false;
	}
	path_ = path;
	name_ = Files::extractFileName(path);

	std::string mask = path;
	mask += Files::PATH_SEPARATOR;
	mask += "*";
	Files::iterator it(mask.c_str()), end;
	for(; it != end; ++it){
		onFileAdded(it->name());
	}
	return true;
}

bool TagNodeDirectory::onFileAdded(const char* name)
{
	std::string fullPath = path_ + Files::PATH_SEPARATOR + name;

	if(Files::isDirectory(fullPath.c_str())){
		if(tagPreferences.skipDirectory(name))
			return false;
		
		TagNodeDirectory* node = new TagNodeDirectory(this);
		if(!node->attach(fullPath.c_str(), false))
			delete node;
		else
			add(node);
		return true;
	}
	else if (tagPreferences.shallBeScanned(name)){
		TagNodeFile* node = new TagNodeFile(this);
		if(!node->attach(fullPath.c_str()))
			delete node;
		else
			add(node);
		return true;
	}
	return false;
}

bool TagNodeDirectory::onFileRemoved(const char* name)
{
	if(TagNode* node = findByName(name)){
		storage()->removeTagsByNode(node);
		remove(node);
	}
	return true;
}

bool TagNodeDirectory::onFileRenamed(const char* oldName, const char* newName)
{
	if(TagNode* node = findByName(oldName))
		node->setName(newName);
	else
		onFileAdded(newName);
	return true;
}

bool TagNodeDirectory::onFileModified(const char* name)
{
	if(TagNode* node = findByName(name))
		node->rescan();
	else
		onFileAdded(name);
	return true;
}

// ---------------------------------------------------------------------------

TagNodeFile::TagNodeFile(TagNodeDirectory* directory)
: TagNode(directory->storage())
{
	setParent(directory);
}


bool TagNodeFile::attach(const char* path)
{
	if(path[0] == '\0')
		return false;
	name_ = Files::extractFileName(path);
	path_ = path;

	return TagNodeFile::rescan();
}

bool TagNodeFile::rescan()
{
	storage()->removeTagsByNode(this);
	if(parseSource(path_.c_str()))
		storage()->addTagsFromNode(this);
	return true;
}
